#include <rid.h>
#include <stdint.h>
#include <stdlib.h>

#define NIT_SHORT_NAMES
#include <nit/macros.h>
#include <nit/palloc.h>
#include <nit/list.h>
#include <nit/set.h>
#include <nit/map.h>
#include <nit/buf.h>
#include <nit/gap.h>
#include <nit/crs.h>

#include "aas.h"

Aas_rule *
aas_rule_new(const Rid id, const Rid add_id, int64_t size)
{
	Aas_rule *rule = palloc(rule);

	pcheck(rule, NULL);

	if (gap_init(&rule->vals, size) < 0) {
		free(rule);
		return NULL;
	}

	rule->next = NULL;
	rid_set(rule->id, id);
	rid_set(rule->add_id, add_id);
	return rule;
}

void
aas_rule_free(Aas_rule *rule)
{
	gap_dispose(&rule->vals);
	free(rule);
}

static int
gap_contains(Nit_crs *crs, Nit_gap *gap, const Rid id)
{
	Rid crs_id;

	crs_init(crs, gap, 0);

	while (!crs_movef(crs, sizeof(Rid))) {
		int cmp_val;

		crs_read(crs, crs_id, sizeof(Rid));

		if (!(cmp_val = rid_cmp(id, crs_id)))
			return 1;

		if (cmp_val > 0) {
			crs_moveb(crs, sizeof(Rid));
			return 0;
		}
	}

	return 0;
}

int
aas_rule_add(Aas_rule *rule, const Rid id)
{
	Nit_crs crs;

	if (gap_contains(&crs, &rule->vals, id))
		return 1;

	return crs_write(&crs, id, sizeof(Rid));
}

int
aas_rule_remove(Aas_rule *rule, const Rid id)
{
	Nit_crs crs;

	if (!gap_contains(&crs, &rule->vals, id))
		return 1;

	crs_erase(&crs, sizeof(Rid));
	return 0;
}

int
aas_rule_same(Aas_rule *rule1, Aas_rule *rule2)
{
	Nit_crs crs1;
	Nit_crs crs2;

	if (rid_cmp(rule1->add_id, rule2->add_id))
		return 0;

	crs_init(&crs1, &rule1->vals, 0);
	crs_init(&crs2, &rule2->vals, 0);

	while (1) {
		int move = crs_movef(&crs1, sizeof(Rid));
		Rid id1;
		Rid id2;

		if (move != crs_movef(&crs2, sizeof(Rid)))
			return 0;

		if (move < 0)
		        break;

		crs_read(&crs1, id1, sizeof(Rid));
		crs_read(&crs2, id2, sizeof(Rid));

		if (rid_cmp(id1, id2))
			return 0;
	}

	return 1;
}


int
aas_set_init(Aas_set *set, Aas_rule *rules, unsigned int sequence)
{
	if (map_init(&set->vals, sequence) < 0)
		return -1;

	set->rules = rules;
	return 0;
}

static void
set_free_func(void *key, void *val, void *extra)
{
	(void) extra;
	(void) key;

	gap_free(val);
}

void
aas_set_dispose(Aas_set *set, Nit_entry **entries)
{
	Aas_rule *rules = set->rules;

	while (rules) {
		Aas_rule *tmp = rules;

		rules = rules->next;
		aas_rule_free(tmp);
	}

	map_recycle(&set->vals, set_free_func, NULL, entries);
	map_dispose(&set->vals);
}

Aas_set *
aas_set_new(Aas_rule *rules, unsigned int sequence)
{
	Aas_set *set = palloc(set);

	pcheck(set, NULL);

	if (aas_set_init(set, rules, sequence) < 0){
		free(set);
		return NULL;
	}

	return set;
}

void
aas_set_free(Aas_set *set, Nit_entry **entries)
{
	aas_set_dispose(set, entries);
	free(set);
}

static int
vals_contains(Nit_map *set_vals, Nit_gap *rule_vals)
{
	Nit_crs crs;

	crs_init(&crs, rule_vals, 0);

	while (!crs_movef(&crs, sizeof(Rid))) {
		Rid crs_id;

		crs_read(&crs, crs_id, sizeof(Rid));

		if (!set_contains(set_vals, crs_id, sizeof(Rid)))
			return 0;
	}

	return 1;
}

static int
evoke_rule(Aas_rule *rule, Aas_set *set, Nit_entry **entries)
{
	if (!vals_contains(&set->vals, &rule->vals))
		return 0;

	if (aas_set_add(set, rule->add_id, rule->id, entries) < 0)
		return -1;

	return 0;
}

int
aas_set_rule(Aas_set *set, Aas_rule *rule, Nit_entry **entries)
{
	if (aas_set_match_rule(set, rule))
		return 1;

	rule->next = set->rules;
	set->rules = rule;
	return evoke_rule(rule, set, entries);
}

Aas_rule *
aas_set_unrule(Aas_set *set, const Rid id, Nit_entry **entries)
{
	Aas_rule **rules = &set->rules;

	for (; *rules; rules = &(*rules)->next)
		if (!rid_cmp((*rules)->id, id)) {
			Aas_rule *rule = *rules;

			*rules = rule->next;
			aas_set_remove(set, rule->add_id, rule->id, entries);
			return rule;
		}

	return NULL;
}

static int
evoke_rules(Aas_set *set, const Rid id, Nit_entry **entries)
{
	Aas_rule *rule = set->rules;
	Nit_crs crs;

	for (; rule; rule = rule->next)
		if (gap_contains(&crs, &rule->vals, id) &&
		    evoke_rule(rule, set, entries) < 0)
		        return -1;

	return 0;
}

int
aas_set_add(Aas_set *set, const Rid id, const Rid rule_id, Nit_entry **entries)
{
	Nit_gap *gap = map_get(&set->vals, id, sizeof(Rid));

	if (gap) {
		Nit_crs crs;

		if (gap_contains(&crs, gap, rule_id))
			return 0;

		return crs_write(&crs, rule_id, sizeof(Rid));
	}

	if (!(gap = gap_new(sizeof(Rid))))
		return -1;

	gap_write(gap, rule_id, sizeof(Rid));

	if (map_add(&set->vals, id, sizeof(Rid), gap, entries) < 0) {
		gap_free(gap);
		return -1;
	}

	if (evoke_rules(set, id, entries) < 0) {
		map_remove(&set->vals, id, sizeof(Rid), entries);
		gap_free(gap);
		return -1;
	}

	return 0;
}

static void
unevoke_rules(Aas_set *set, const Rid id, Nit_entry **entries)
{
	Aas_rule *rule = set->rules;
	Nit_crs crs;

	for (; rule; rule = rule->next)
		if (gap_contains(&crs, &rule->vals, id))
			aas_set_remove(set, rule->add_id, rule->id, entries);

	return;
}

void
aas_set_remove(Aas_set *set, const Rid id, const Rid rule_id,
	       Nit_entry **entries)
{
	Nit_gap *gap = map_get(&set->vals, id, sizeof(Rid));
	Nit_crs crs;

	if (!gap || !gap_contains(&crs, gap, rule_id))
		return;

	crs_erase(&crs, sizeof(Rid));

	if (gap_len(gap))
		return;

	map_remove(&set->vals, id, sizeof(Rid), entries);
	gap_free(gap);
	unevoke_rules(set, id, entries);
}

Aas_rule *
aas_set_match_rule(Aas_set *set, Aas_rule *rule)
{
	Aas_rule *rules = set->rules;

	for (; rules; rules = rules->next)
		if (aas_rule_same(rule, rules))
			return rules;

	return NULL;
}

Aas_rule *
aas_set_get_rule(Aas_set *set, const Rid id)
{
	Aas_rule *rules = set->rules;

	for (; rules; rules = rules->next)
		if (!rid_cmp(rules->id, id))
			return rules;

	return NULL;
}
